﻿/*======================================================================
== Copyright : BlueCurve (c)
== Licence   : Gnu/GPL v2.x
== Author    : Teddy Albina
== Email     : bluecurveteam@gmail.com
== Web site  : http://www.codeplex.com/BlueCurve
========================================================================*/
using System;
using System.AddIn;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Cache;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Xml.Linq;
using BlueCurve.AddinView;
using BlueCurve.DownloadAttributes;
using BlueCurve.EnumAction;
using BlueCurve.WebCrawlerRequest;
using System.Threading;
using BlueCurve.Common;
using BlueCurve.Common.Threads;
using System.Diagnostics;
using nftplib;
using System.Collections.Generic;

namespace Addins.Ftp
{
    /// <summary>
    /// Addin permettant d'effetuer des requêtes Ftp
    /// </summary>
    [AddIn("AddinsFtp",
    Description = "This addin can do FTP request",
    Version = "1.0.0.0",
    Publisher = "BlueCurve (c) Project Team")]
    public class AddinsFtp : AddinProtocolViewAddinsSide
    {
        #region 'Configuration'

        /// <summary>
        /// Retourne/définit les type de fichiers acceptés
        /// </summary>
        public override string Accept { get; set; }
        /// <summary>
        /// Retourne/définit l'adresse d'un eventuel proxy
        /// </summary>
        public override Uri Proxy { get; set; }
        /// <summary>
        /// Objet du pool de threads
        /// </summary>
        private SimpleLockThreadPool ThreadPool = new SimpleLockThreadPool(Environment.ProcessorCount * 15);
        /// <summary>
        /// Objet du client ftp
        /// </summary>
        private FtpClient fTpClient { get; set; }
        /// <summary>
        /// Nombre de téléchargements en // sur le serveur ftp
        /// </summary>
        private int FtpSimultaneousDownloads = -1;
        /// <summary>
        /// Nombre de téléchargements actuellement en cours
        /// </summary>
        private long CurrentDownloads = 0;

        #endregion






        #region 'Function'

        /// <summary>
        /// Effectue des requetes HTTP
        /// </summary>
        /// <param name="downloadata">Données concernant le document à télécharger</param>
        /// <param name="certificates">Certificats X509</param>
        /// <param name="certificates2">Certificats X509V2</param>
        /// <returns>BlueCurve.WebCrawlerRequest.RequestState</returns>
        public override RequestState Download(Attributes downloadata, X509Certificate[] certificates, X509Certificate[] certificates2)
        {
            BlueCurve.WebCrawlerRequest.RequestState rs = new BlueCurve.WebCrawlerRequest.RequestState()
            {
                Options = downloadata,
                TypeOffRequest = BlueCurve.WebCrawlerRequest.RequestType.ftp,
                Uri = new Uri(downloadata.Url),
                Action = downloadata.Action,
                FtpSimultaneousDownloads = downloadata.FtpSimultaneousDownloads
            };

            FtpSimultaneousDownloads = downloadata.FtpSimultaneousDownloads;

            // Création du client ftp
            fTpClient = MakeFtpConnection(ref downloadata);
            // On quitte en cas d'erreur
            if (fTpClient == null)
                return null;

            // Création d'une instance de proxy
            MakeWebProxy(downloadata.Options);
            // Ajout d'un timeout
            fTpClient.Timeout = 30 * 1000;
            // Activation du ssl
            fTpClient.EnableSSL = GetSsl(downloadata.Options);

            // Listes des fichiers dans le répertoire de base
            List<string> _baseDirectory = fTpClient.GetDirectoryList("/");

            _baseDirectory.ForEach(
                delegate(string file)
                {
                    ThreadPool.QueueUserWorkItem(new WaitCallback(GetAllFiles), file);
                });

            return null;
        }


        /// <summary>
        /// Méthode qui récupère tous les fichiers et tous les répertoires. Elle utilise un Stack 
        /// car c'est la meilleur façon de faire avec un algo de scan non recursif
        /// </summary>
        /// <param name="inDirectory">Répertoir à parcourire</param>
        private void GetAllFiles(object state)
        {
            string inDirectory = state as string;
            Stack<string> myStack = new Stack<string>();
            
            // Récupération des sous répertoires
            fTpClient.GetDirectoryList(inDirectory).ForEach(
                delegate(string fileName)
                {
                    myStack.Push(fileName);
                });

            string poped = null;

            while (myStack.Count > 0)
            {
                poped = myStack.Pop();

                if (fTpClient.IsDirectoryExists(poped))
                {
                    try
                    {
                        fTpClient.GetDirectoryList(poped).ForEach(
                            delegate(string fileName)
                            {
                                myStack.Push(fileName);
                            });
                    }
                    catch
                    { }
                }
                else if (fTpClient.IsFileExists(poped))
                {
                    if (FtpSimultaneousDownloads <= 0)
                    {
                        fTpClient.DownloadFile(poped, StreamFactory.CreateStream()); 
                    }
                    else
                    {
                        if (Interlocked.Read(ref CurrentDownloads) < FtpSimultaneousDownloads)
                        {
                            Interlocked.Increment(ref CurrentDownloads);
                            fTpClient.DownloadFile(poped, StreamFactory.CreateStream());
                            Interlocked.Decrement(ref CurrentDownloads);
                        }
                    }
                }
            }
        }


        /// <summary>
        /// Retourne un boolean pour l'activation du ssl
        /// </summary>
        /// <param name="data">Chaine XML</param>
        /// <returns>bool</returns>
        private bool GetSsl(string data)
        {
            try
            {
                XDocument doc = XDocument.Load(new StringReader(data));

                return Convert.ToBoolean((from x in doc.Descendants()
                                          select x.Element("ssl").Value).Single()); ;
            }
            catch
            {
                return false;
            }
        }

        /// <summary>
        /// Fabrique un objet de type webproxy
        /// </summary>
        /// <param name="data">Données à utiliser pour la conception du proxy</param>
        /// <param name="webrequest">Réference vers l'objet HttpWebRequest en cours</param>
        private void MakeWebProxy(string data)
        {
            WebProxy proxy = new WebProxy(Proxy);
            try
            {
                NetworkCredential credential = new NetworkCredential();

                XDocument doc = XDocument.Load(new StringReader(data));

                var cred = from x in doc.Descendants("proxy")
                           select new
                           {
                               Bypassforlocal = x.Element("bypassforlocal").Value,
                               Default = x.Element("default").Value,
                               Computer = x.Element("computer").Value,
                               Password = x.Element("password").Value,
                               Id = x.Element("id").Value
                           };

                foreach (var x in cred)
                {
                    if (!string.IsNullOrEmpty(x.Default) && x.Default.Equals("true", StringComparison.InvariantCultureIgnoreCase))
                    {
                        proxy.UseDefaultCredentials = true;
                    }
                    else
                    {
                        if (!string.IsNullOrEmpty(x.Id))
                        {
                            credential.UserName = x.Id;
                            if (!string.IsNullOrEmpty(x.Password))
                                credential.Password = x.Password;
                            if (!string.IsNullOrEmpty(x.Computer))
                                credential.Domain = x.Computer;

                            proxy.Credentials = credential;
                        }
                    }
                    if (!string.IsNullOrEmpty(x.Bypassforlocal))
                        proxy.BypassProxyOnLocal = bool.Parse(x.Bypassforlocal);
                }

                // Set the proxy
                fTpClient.Proxy = proxy;
            }
            catch
            {
                fTpClient.Proxy = proxy;
            }
        }


        /// <summary>
        /// Créer la connexion à l'hote 
        /// </summary>
        /// <param name="downloadata">Données du téléchargement</param>
        /// <param name="data"></param>
        /// <returns>FtpClient</returns>
        private FtpClient MakeFtpConnection(ref Attributes downloadata)
        {
            try
            {
                // Récupération des informations d'authentification
                string[] auth = MakeCredential(downloadata.Options;
                bool _needauth = false;
                if (!string.IsNullOrEmpty(auth[0]) && !string.IsNullOrEmpty(auth[1]))
                    _needauth = true;

                // Récupération du port
                string[] hostIp = downloadata.Url.Split(':');

                IPAddress _ip;
                if (IPAddress.TryParse(hostIp[0], out _ip))
                {
                    if (!_needauth)
                        return new FtpClient(new IPEndPoint(_ip, Convert.ToInt32(hostIp[1] ?? "80")));
                    else
                        return new FtpClient(new IPEndPoint(_ip, Convert.ToInt32(hostIp[1] ?? "80")), auth[1], auth[0]);
                }
                else
                {
                    if (!_needauth)
                        return new FtpClient(hostIp[0], auth[1], auth[0]);
                    else
                        return new FtpClient(hostIp[0]);
                }
            }
            catch
            {
                return null;
            }
        }


        /// <summary>
        /// Renvoie les informations d'authentification
        /// </summary>
        /// <param name="data">Options attachés à la requête</param>
        /// <returns>string[]</returns>
        private string[] MakeCredential(string data)
        {
            try
            {
                XDocument doc = XDocument.Load(new StringReader(data));

                var cred = from x in doc.Descendants("credential")
                           select new
                           {
                               Password = x.Element("password").Value,
                               Id = x.Element("id").Value
                           };

                string password = null;
                string username = null;
                foreach (var m in cred)
                {
                    password = m.Password;
                    username = m.Id;
                }

                return (new string[] { password, username });
            }
            catch
            {
                return null;
            }
        }

        #endregion
    }
}
